/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package model;

import annotation.OptionalParameter;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import exception.InvalidTestScriptPlaybackCommandException;
import exception.InvalidSTAFCommandException;
import exception.BuildNameNotSetException;
import exception.InvalidCommandException;
import exception.InvalidCommandResultReferenceException;
import exception.InvalidHostNameException;
import exception.MachineInfoNotCompleteException;
import exception.NoDefaultMachineDefinedException;
import exception.RFTInstallPathNotFoundException;
import exception.ScriptPlaybackFailureException;
import exception.TestScriptAlreadyExecutedException;
import exception.VMNotSetupException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import model.provider.TestScriptProvider;
import util.FileSystemWatcher;

/**
 *
 * @author pyin
 */
public class Command implements Executable {

    private MachineManager machineFacotry;
    private ConfigManager configManager;
    private CommandResult result;
    private String cmd;
    private CommandExecutionThread thread;
    private int status;
    private Machine machine = null;
    private TestScriptManager testScriptManager;
    private CommandResultManager cmdResultManager;
    private ServerUtil serverUtil;
    TestScriptProvider testScriptProvider;

    @Inject
    public Command(
            MachineManager machineFactory,
            ConfigManager configManager,
            TestScriptManager scriptManager,
            CommandResultManager resultManager,
            ServerUtil serverUtil,
            TestScriptProvider testScriptProvider) {

        this.machineFacotry = machineFactory;
        this.testScriptManager = scriptManager;
        this.cmdResultManager = resultManager;
        this.configManager = configManager;
        this.serverUtil = serverUtil;
        this.testScriptProvider = testScriptProvider;
    }

    public String getCmd() {
        return cmd;
    }

    public void setCmd(String cmd) {
        this.cmd = cmd;
    }

    public boolean isSucceed() {
        return this.result.isSucceed();
    }

    public void setResult(CommandResult result) {
        this.result = result;
    }

    public String getOutput() {
        return this.result.getOutput();
    }

    public void setStatus(int stat) {
        this.status = stat;
    }

    public void setMachine(Machine machine) {
        this.machine = machine;
    }

    public Machine getMachine() {
        return this.machine;
    }

    //Be careful with the regular expression!!!
    private final String[] patterns = {
        "(ls)\\s+([^\\s]+)\\s+([^\\s]+)+$",
        "(ls)\\s+([^\\s]+)",
        "(sleep)\\s+(\\d+)",
        "(qs)\\s+([^\\s]+)",
        "(lgs)\\s+([^\\s]+)\\s+([^\\s]+)\\s+onComplete:([^\\s]+)",
        "(lgs)\\s+([^\\s]+)\\s+onComplete:([^\\s]+)",
        "(echo)\\s+([^\\s]+)$",
        "(go)\\s+([^\\s]+)\\s+$",
        "(up)\\s+([-\\w\\[\\]|]+)$",
        "(up)",
        "(lm)\\s+([-\\w\\[\\]|]+)$",
        "(lm)",
        "(use)\\s+([-\\w\\[\\]|]+)$",
        "(sm)\\s+([-\\w\\[\\]|]+)$",
        "(sm)",
        "(qm)\\s+([-\\w\\[\\]|]+)$",
        "(qm)",
        "(machine)",
        "(help)$",
        "(rc)$",
        "(setup)(\\s+[-\\w\\[\\]|]+)?$",
        "(setup)",
        "(reset)\\s+([-\\w\\[\\]|]+)$",
        "(reset)",
        "(sb)\\s+([-\\w\\[\\]|]+)$",
        "(watch)\\s+([-_\\.\\*\\+?\\w\\[\\]|]+)$",
        "(showme)\\s+([-\\w\\[\\]|]+)$",
        "(an)\\s+([-\\w\\[\\]|]+)$",
        "(qs)\\s+([-\\w\\[\\]|]+)$"
    };

    public boolean isValid() {
        for (int i = 0; i < patterns.length; i++) {
            Pattern p = Pattern.compile(patterns[i]);
            Matcher m = p.matcher(this.cmd.toLowerCase());
            if (m.find()) {
                return true;
            }
        }
        return false;
    }

    public boolean isRunOnSameMachine(Command cmd) {
        return this.getMachine().equals(cmd.getMachine());
    }

    public void an(String notification) {
        NotificationManager.addNotification("an " + notification, notification);
        try {
            Thread.sleep(10000);
        } catch (InterruptedException ex) {
            Logger.getLogger(Command.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.setResult(new CommandResult(0, "New notification is created"));
    }

    public void use(String machine) {
        try {
            Machine m = this.machineFacotry.getMachine(machine);
            this.configManager.setMachine(machine);
            this.setResult(new CommandResult(0, "Machine " + machine + " is defined as you default machine"));
        } catch (MachineInfoNotCompleteException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidHostNameException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        }
    }

    public void sleep(String seconds) {
        try {
            int sec = new Integer(seconds).intValue();
            Thread.sleep(1000 * sec);
            this.setResult(new CommandResult(0, "slept for " + sec + " seconds"));
        } catch (InterruptedException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        }
    }

    public void machine() {
        String m = this.configManager.getMachine();
        if (m != null) {
            this.setResult(new CommandResult(0, m));
        } else {
            this.setResult(new CommandResult(1, "Not defined"));
        }
    }

    public void rc() {
        if (this.configManager.reload() && this.machineFacotry.reload()) {
            this.setResult(new CommandResult(0, "All configurations are reloaded."));
        } else {
            this.setResult(new CommandResult(1, "Error in reloading configurations."));
        }
    }

    @OptionalParameter(0)
    public void up(String hostName) {
        try {
            Machine machine = this.machineFacotry.getMachine(hostName);
            if (serverUtil.createServerSideProjectArchive() && machine.getProjectArchive() && machine.extractProjectArchive()) {
                this.setResult(new CommandResult(0, "Sucessfully update project on machine " + hostName));
            } else {
                this.setResult(new CommandResult(1, "Failed to update project on machine " + hostName));
            }
        } catch (MachineInfoNotCompleteException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidHostNameException ex) {
            Logger.getLogger(Command.class.getName()).log(Level.SEVERE, null, ex);
            this.setResult(new CommandResult(1, ex.getMessage()));
        }

    }

    public void echo(String msg) {
        this.setResult(new CommandResult(0, msg));
    }

    @OptionalParameter(0)
    public void reset(String m) {
        try {
            Machine machine = this.machineFacotry.getMachine(m);
            if (machine instanceof RealMachine) {
                this.setResult(new CommandResult(1, machine.getName() + " is a real machine."));
            } else {
                VMMachine vm = (VMMachine) machine;
                int result = vm.restore(this.configManager.getBuildName());
                if (result == 0) {
                    this.setResult(new CommandResult(0, "Machine  " + m + " has been successfully restored."));
                }
                if (result == 1) {
                    this.setResult(new CommandResult(1, "Failed in reverting snapshot."));
                }
                if (result == 2) {
                    this.setResult(new CommandResult(1, "Failed in starting machine."));
                }
                if (result == 3) {
                    this.setResult(new CommandResult(1, "Failed in creating server side project archive."));
                }
                if (result == 4) {
                    this.setResult(new CommandResult(1, "Failed in copying project archive."));
                }
                if (result == 5) {
                    this.setResult(new CommandResult(1, "Failed in extracting project archive."));
                }
            }
        } catch (MachineInfoNotCompleteException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidHostNameException ex) {
            Logger.getLogger(Command.class.getName()).log(Level.SEVERE, null, ex);
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (BuildNameNotSetException e) {
            this.setResult(new CommandResult(1, e.getMessage()));
        }

    }

    public void watch(String expectedPattern) {
        try {
            FileSystemWatcher watcher = new FileSystemWatcher(this.configManager.getBiuldReleasePath(), expectedPattern, this.configManager.getNewBuildWatchFrequency(), true);
//            FileSystemWatcher watcher = new FileSystemWatcher(configManager.getBiuldReleasePath(), expectedPattern, 600, true);
            watcher.start();
            this.setResult(new CommandResult(0, watcher.getDetected()));
        } catch (IOException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        }
    }

    private boolean ifDirContainsFile(String dir, String fileName) {
        File currentDir = new File(dir);
        if (!currentDir.exists()) {
            return false;
        }
        String[] allFiles = currentDir.list();
        for (int i = 0; i < allFiles.length; i++) {
            if (allFiles[i].toLowerCase().endsWith(fileName.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    //It's a very bad idea to invoke another command from within a command.
    public void sb(String buildName) {
        File root = new File(this.configManager.getInstallerPath());
        if (ifDirContainsFile(this.configManager.getInstallerPath(), buildName)) {
            this.configManager.setBuildName(buildName);
            this.setResult(new CommandResult(0, "BuildName already set to " + buildName));
        } else {
            File releaseDir = new File(this.configManager.getBiuldReleasePath());
            String file = null;
            if (ifDirContainsFile(this.configManager.getBiuldReleasePath(), buildName)) {
                file = this.configManager.getBiuldReleasePath() + "\\" + buildName;
            }
//            else {
//                file = NotificationManager.getNotification("watch " + buildName);
//            }
            if (file != null) {
                try {
                    File f = new File(file);
                    if (f.isFile()) {
                        copyFile(f, new File(this.configManager.getInstallerPath() + "\\" + f.getAbsolutePath().substring(f.getAbsolutePath().lastIndexOf("\\"), f.getAbsolutePath().length())));
                    }
                    if (f.isDirectory()) {
                        copyDir(f, new File(this.configManager.getInstallerPath() + "\\" + buildName));
                    }
                    this.configManager.setBuildName(buildName);
                    this.setResult(new CommandResult(0, "New build is copied to the installer path and build name is set to " + buildName));
                } catch (IOException ex) {
                    this.setResult(new CommandResult(1, "IOException thrown when copying file" + file));
                    NotificationManager.addNotification("sb " + buildName, "IOException thrown when copying file" + file);
                }
            } else {
                this.setResult(new CommandResult(1, "Specified build not found in the release directory"));
            }
//            else {
//                if (!CommandRunner.isCommandRunning("watch " + buildName) && !CommandRunner.isCommandQueued("watch "+buildName)) {
//                    Command command = new Command("watch " + buildName);
//                    CommandRunner.addCommand(command);
//                    sb(buildName);
//                }
//            }
        }
    }

    private void copyFile(File s, File t) throws IOException {

        FileChannel in = (new FileInputStream(s)).getChannel();
        FileChannel out = (new FileOutputStream(t)).getChannel();
        in.transferTo(0, s.length(), out);
        in.close();
        out.close();
    }

    private void copyDir(File sourceLocation, File targetLocation) throws IOException {
        if (sourceLocation.isDirectory()) {
            if (!targetLocation.exists()) {
                targetLocation.mkdir();
            }

            String[] children = sourceLocation.list();
            for (int i = 0; i < children.length; i++) {
                copyDir(new File(sourceLocation, children[i]),
                        new File(targetLocation, children[i]));
            }
        } else {

            InputStream in = new FileInputStream(sourceLocation);
            OutputStream out = new FileOutputStream(targetLocation);

            // Copy the bits from instream to outstream
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            in.close();
            out.close();
        }
    }

    @OptionalParameter(0)
    public void setup(String hostName) {
        try {
            this.machine = this.machineFacotry.getMachine(hostName);
            if (machine instanceof VMMachine) {
                VMMachine vm = (VMMachine) machine;
                if (vm.hasCurrentBuildSnapshot()) {
                    this.setResult(new CommandResult(0, this.machine.getName() + " has already been setup"));
                } else {
                    int result = vm.setupEnv(this.configManager.getBuildName());
                    switch (result) {
                        case 1:
                            this.setResult(new CommandResult(1, "Failed to revert to clean snapshot"));
//                        NotificationManager.addNotification(this.cmd, this.output);
                            break;
                        case 2:
                            this.setResult(new CommandResult(1, "Failed to start machine " + hostName));
//                        NotificationManager.addNotification(this.cmd, this.output);
                            break;
                        case 3:
                            this.setResult(new CommandResult(1, "Failed to create RFT project zip file"));
//                        NotificationManager.addNotification(this.cmd, this.output);
                            break;
                        case 4:
                            this.setResult(new CommandResult(1, "Failed to copy RFT project to target machine " + hostName));
//                        NotificationManager.addNotification(this.cmd, this.output);
                            break;
                        case 5:
                            this.setResult(new CommandResult(1, "Failed to extract RFT project"));
//                        NotificationManager.addNotification(this.cmd, this.output);
                            break;
                        case 6:
                            this.setResult(new CommandResult(1, "Failed to copy installer archive"));
//                        NotificationManager.addNotification(this.cmd, this.output);
                            break;
                        case 7:
                            this.setResult(new CommandResult(1, "Failed to copy extract installer archive"));
//                        NotificationManager.addNotification(this.cmd, this.output);
                            break;
                        case 8:
                            this.setResult(new CommandResult(1, "Failed to shut down machine " + hostName));
//                        NotificationManager.addNotification(this.cmd, this.output);
                            break;
                        case 9:
                            this.setResult(new CommandResult(1, "Failed to create snapshot " + this.configManager.getBuildName()));
//                        NotificationManager.addNotification(this.cmd, this.output);
                            break;
                        case 10:
                            this.setResult(new CommandResult(1, "Failed to start machine " + hostName));
//                        NotificationManager.addNotification(this.cmd, this.output);
                            break;
                        case 0:
                            this.setResult(new CommandResult(0, "Done"));
                            break;
                    }
                }
            } else {
                this.setResult(new CommandResult(1, machine.getName() + " is a real machine."));
            }
        } catch (MachineInfoNotCompleteException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidHostNameException e) {
            this.setResult(new CommandResult(1, e.getMessage()));
        } catch (BuildNameNotSetException e) {
            this.setResult(new CommandResult(1, e.getMessage()));
        }

    }

    public void qs(String pattern) {
        try {
            List scripts = testScriptManager.getScriptNames(pattern);
            Iterator it = scripts.iterator();
//            this.output = "";
//            while (it.hasNext()) {
//                this.output = this.output + it.next().toString();
//                this.output = this.output + "<br>";
//            }
//            this.result = 0;
            this.setResult(new CommandResult(0, scripts.size() + " scripts", scripts));
        } catch (FileNotFoundException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        }

    }

//    public void qsa() {
//        try {
//            String text = "";
//            List scripts = TestScriptManager.getAllScriptNames();
//            Iterator it = scripts.iterator();
//            while (it.hasNext()) {
//                String line = ((String) it.next()).replace(".java", "");
//                text = text + line + "<br>";
//            }
//            this.output = text;
//            this.result = 0;
//        } catch (FileNotFoundException ex) {
//            this.output = "Script folder not found";
//            this.result = 1;
//        }
//    }
    private String[] getPatterns() {
        return this.patterns;
    }

    @OptionalParameter(1)
    public void lgs(String groupPattern, String hostname, String linkCmd) {
        Pattern pattern = Pattern.compile(groupPattern);
        try {
//            List allNames = TestScriptManager.getAllScriptNames();
//            Iterator it = allNames.iterator();
//            List<TestScript> allScripts = new ArrayList<TestScript>();
//            while (it.hasNext()) {
//                TestScript script = new TestScript(it.next().toString(), hostname);
//                if (groupPattern.equalsIgnoreCase("all") || groupPattern.equals("*")) {
//                    allScripts.add(script);
//                } else {
//                    Matcher matcher = pattern.matcher(script.getScriptName());
//                    if (matcher.find()) {
//                        allScripts.add(script);
//                    }
//                }
//            }
            List scriptNames = testScriptManager.getScriptNames(groupPattern);
            List allScripts = new ArrayList();
            Iterator namesIterator = scriptNames.iterator();
            while (namesIterator.hasNext()) {
                String name = (String) namesIterator.next();
                TestScript script = this.testScriptProvider.get(name, hostname);
//                TestScript script = new TestScript(name, hostname);
                allScripts.add(script);
            }

            String cmd = linkCmd + " " + hostname;
            TestScriptGroup group = new TestScriptGroup(allScripts, cmd);
            this.setResult(new CommandResult(0, group.getScriptCount() + " scripts have been executed in batch execution."));
            group.run();
        } catch (MachineInfoNotCompleteException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidHostNameException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (FileNotFoundException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (BuildNameNotSetException e) {
            this.setResult(new CommandResult(1, e.getMessage()));
        }

    }

    @OptionalParameter(1)
    public void ls(String scriptName, String hostname) {
//        System.out.println("ls " + scriptName);
        try {
            TestScript script = this.testScriptProvider.get(scriptName, hostname);
            script.run();
            String result = script.getResult().isPassed() ? "passed" : "failed";
            this.setResult(new CommandResult(0, "Script [" + script.getScriptName() + "] " + result));
            NotificationManager.addNotification("ls", this.getResult().getOutput());
        } catch (MachineInfoNotCompleteException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (ScriptPlaybackFailureException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidTestScriptPlaybackCommandException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (VMNotSetupException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (BuildNameNotSetException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (TestScriptAlreadyExecutedException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidSTAFCommandException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (RFTInstallPathNotFoundException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidHostNameException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        }
    }

//    public void help() {
//        FileReader fr = null;
//        try {
//            fr = new FileReader(configManager.getUsageFilePath());
//            BufferedReader br = new BufferedReader(fr);
//            String line;
//            while ((line = br.readLine()) != null) {
//                this.output = this.output + line + "<br>";
//            }
//            System.out.println(this.output);
//            this.result = 0;
//        } catch (IOException ex) {
//            this.result = 1;
//            this.output = "Cannot find file usage.txt";
//        } finally {
//            try {
//                fr.close();
//            } catch (IOException ex) {
//                Logger.getLogger(Command.class.getName()).log(Level.SEVERE, null, ex);
//            }
//        }
//    }
    @OptionalParameter(0)
    public void lm(String machineName) {
        try {
            Machine machine = this.machineFacotry.getMachine(machineName);
            this.machine = machine;
            if (machine == null) {
                this.setResult(new CommandResult(1, "The machine is not on list, please try another one"));
            } else {
                if (machine instanceof RealMachine) {
                    this.setResult(new CommandResult(1, machine.getName() + " is a physical machine."));
                } else {
                    if (((VMMachine) machine).start()) {
                        this.setResult(new CommandResult(0, machineName + " started."));
                    } else {
                        this.setResult(new CommandResult(1, "Failed to start " + machineName));
                    }
                }
            }
        } catch (MachineInfoNotCompleteException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidHostNameException e) {
            this.setResult(new CommandResult(1, e.getMessage()));
        }
    }

    @OptionalParameter(0)
    public void sm(String machineName) {
        try {
            Machine machine = this.machineFacotry.getMachine(machineName);
            this.machine = machine;
            if (machine == null) {
                this.setResult(new CommandResult(1, "The machine is not on list, please try another one"));
            } else {
                if (machine instanceof RealMachine) {
                    this.setResult(new CommandResult(1, machine.getName() + " is a physical machine."));
                } else {
                    if (((VMMachine) machine).start()) {
                        this.setResult(new CommandResult(0, "Start machine successfully."));
                    } else {
                        this.setResult(new CommandResult(1, "Start machine failed"));
                    }
                }
            }
        } catch (MachineInfoNotCompleteException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidHostNameException e) {
            this.setResult(new CommandResult(1, e.getMessage()));
        }
    }

    @OptionalParameter(0)
    public void qm(String machineName) {
        try {
            if (machineName != null) {
                if (machineName.trim().equalsIgnoreCase("all")) {
                    List machines = this.machineFacotry.getVMMachines();
                    Iterator it = machines.iterator();
                    String result = "";
                    while (it.hasNext()) {
                        Machine machine = (Machine) it.next();
                        result = result + machine.getName() + "<br>";
                    }
                    this.setResult(new CommandResult(0, result));
                } else {
                    Machine machine = this.machineFacotry.getMachine(machineName);
                    if (machine instanceof VMMachine) {
                        VMMachine vmachine = (VMMachine) machine;
                        String status = vmachine.isHostingMachineReady() ? "running" : "stopped";
                        if (status.equals("running")) {
                            String[] snapshots = vmachine.getSnapshots();
                            if (snapshots != null) {
                                String ss = "";
                                if (snapshots != null) {
                                    for (int i = 0; i < snapshots.length; i++) {
                                        ss = ss + snapshots[i] + "<br>";
                                    }
                                }
                                String result = "hostname:\t" + vmachine.getName() + "<br>" + "host on:\t" + vmachine.getVmHost() + status + "<br>" + "OS:\t" + vmachine.getOs() + " <br>snapshots:" + ss;
                                this.setResult(new CommandResult(0, result));
                            } else {
                                this.setResult(new CommandResult(1, "Failed to retrieve machine status"));
                            }
                        } else {
                            this.setResult(new CommandResult(1, "hosting machine is not ready"));
                        }
                    } else {
                        RealMachine rm = (RealMachine) machine;
                        if (rm.isStarted()) {
                            this.setResult(new CommandResult(0, rm.getName() + " is a physical running machine."));
                        } else {
                            this.setResult(new CommandResult(1, rm.getName() + " is a physical stopped machine."));
                        }
                    }
                }
            } else {
                throw new NoDefaultMachineDefinedException();
            }
        } catch (MachineInfoNotCompleteException ex) {
            this.setResult(new CommandResult(1, ex.getMessage()));
        } catch (InvalidHostNameException e) {
            this.setResult(new CommandResult(1, e.getMessage()));

        } catch (NoDefaultMachineDefinedException e) {
            this.setResult(new CommandResult(1, e.getMessage()));
        }

    }

    public void runTillFinish() throws InvalidCommandException, InvalidCommandResultReferenceException, InterruptedException {
        run();
        while (!finished()) {
            Thread.sleep(100);
        }
    }

    public void run() throws InvalidCommandException, InvalidCommandResultReferenceException {
        boolean foundMethod = false;
        for (int i = 0; i < patterns.length; i++) {
            Pattern p = Pattern.compile(patterns[i]);
            Matcher m = p.matcher(cmd);
//            Matcher m = p.matcher(cmd.toLowerCase());
            if (m.matches()) {
//            if (m.find()) {
                String method = m.group(1);
                String[] args = new String[m.groupCount() - 1];
                for (int j = 0; j < args.length; j++) {
                    args[j] = m.group(j + 2);
                }
                Method[] methods = this.getClass().getMethods();
                for (int j = 0; j < methods.length; j++) {
                    String methodString = methods[j].getName();
                    if (methodString.equalsIgnoreCase(method)) {
                        foundMethod = true;
                        thread = new CommandExecutionThread(this, methods[j], args);
                        thread.start();
                        if (this.status == -2) {
                            throw new InvalidCommandException();
                        }
//                        while (thread.getState() != State.TERMINATED) {
//                            try {
//                                Thread.sleep(200);
//                            } catch (InterruptedException ex) {
//                                Logger.getLogger(Command.class.getName()).log(Level.SEVERE, null, ex);
//                            }
//                        }
                    }
                }

            }
        }
        if (!foundMethod) {
            throw new InvalidCommandException();
        }
    }

    public boolean finished() {
        return (this.result != null);
//        return this.thread.getState()==State.TERMINATED;
    }

    /**
     * @return the result
     */
    public CommandResult getResult() {
        return result;
    }

    @Singleton
    class CommandExecutionThread extends Thread {

        Command parent;
        Object[] args;
        Method method;
        String result;

        public CommandExecutionThread(Command command, Method method, Object[] parameters) throws InvalidCommandResultReferenceException {
            this.parent = command;
            this.args = parameters;
            this.method = method;
            handleOptionalParameter();
            handleVariableParamenter();
        }

        private void handleVariableParamenter() throws InvalidCommandResultReferenceException {
            try {
                for (int i = 0; i < this.args.length; i++) {
                    Pattern dataPattern = Pattern.compile("\\$(\\d+)d(\\d+)");
                    Matcher matcher = dataPattern.matcher((String) args[i]);
                    if (matcher.find()) {
                        System.out.println(matcher.group(0));
                        int commandIndex = new Integer(matcher.group(1)).intValue() - 1;
                        int dataIndex = new Integer(matcher.group(2)).intValue() - 1;
                        String commandResult = cmdResultManager.getCommand(commandIndex).getResult().getData(dataIndex);
                        this.args[i] = commandResult;
//                    ((String)this.args[i]).replace(matcher.group(0), commandResult);
                    }
                    Pattern outputPattern = Pattern.compile("\\$(\\d+)");
                    matcher = outputPattern.matcher((String) args[i]);
                    if (matcher.matches()) {
                        int commandIndex = new Integer(matcher.group(1)).intValue() - 1;
                        String commandOutput = cmdResultManager.getCommand(commandIndex).getResult().getOutput();
                        this.args[i] = commandOutput;
                    }
                }
            } catch (java.lang.ArrayIndexOutOfBoundsException e) {
//            } catch (NullPointerException e) {
                throw new InvalidCommandResultReferenceException();
            }
        }

        @Override
        public void run() {
            try {
                method.invoke(this.parent, this.args);
//                result = (String) method.invoke(this.parent, this.args);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(Command.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalArgumentException ex) {
//                this.parent.setStatus(-2);
                this.parent.setResult(new CommandResult(1, new NoDefaultMachineDefinedException().getMessage()));
            } catch (InvocationTargetException ex) {
                if (ex.getCause() instanceof java.lang.StringIndexOutOfBoundsException) {
                    this.parent.setStatus(-2);
                }
                Logger.getLogger(Command.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        private String getResult() {
            return result;
        }

        private void handleOptionalParameter() {
            //Add configManager.getMachine to args if optional parameter not exist.
            if (configManager.getMachine() != null) {
//                System.out.println("handleOptionalParameter:defualtMachine:" + configManager.getMachine());
                int definition = this.method.getParameterTypes().length;
//                System.out.println("definition:" + definition + "\targs:" + this.args.length);
                for (int i = 0; i < this.args.length; i++) {
//                    System.out.println("arg:" + this.args[i].toString());
                }
                if (definition == this.args.length + 1) {
                    int index = this.method.getAnnotation(OptionalParameter.class).value();
                    Object[] para = new Object[definition];
                    if (definition == 1) {
                        para[0] = configManager.getMachine();
//                        System.out.println(para[0]);
                    } else {
                        for (int i = 0; i < this.args.length; i++) {
                            if (i < index) {
                                para[i] = this.args[i];
                            } else {
                                para[i + 1] = this.args[i];
                            }
                        }
                        para[index] = configManager.getMachine();
                    }
                    this.args = para;
//                    System.out.println("result:" + this.args.length);
                } else {
                    //do not add optional machine
//                    System.out.println("Stay down");
                }
            }
        }
    }

    public static void main(String[] args) {
        try {
            Injector injector = Guice.createInjector();
//            Command cmd3 = injector.getInstance(Command.class);
//            cmd3.setCmd("qm");
//            cmd3.runTillFinish();
//            System.out.println(cmd3.getResult().getOutput());
//            Command cmd = injector.getInstance(Command.class);
//            cmd.setCmd("use win03uepinstall");
//            cmd.runTillFinish();
//            System.out.println(cmd.getResult().getOutput());
            Command cmd2 = injector.getInstance(Command.class);
            cmd2.setCmd("qm unkown");
            cmd2.runTillFinish();
            System.out.println(cmd2.getResult().getOutput());
        } catch (InvalidCommandException ex) {
            Logger.getLogger(Command.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidCommandResultReferenceException ex) {
            Logger.getLogger(Command.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InterruptedException ex) {
            Logger.getLogger(Command.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

